"""

.. module:: uri
.. moduleauthor:: @author: 

This module contains all Uri classes and declarations. 

Namespaces:
    dc - http://purl.org/dc/terms/
    yarra - http://yarra-project.com/element/
    eplanning - http://eplanning.vic.gov.au/terms/

.. seealso:: modules :mod:`<module>`

    Uniform Resource Identifier (URI): Generic Syntax
    http://www.ietf.org/rfc/rfc3986.txt
    
    A URI can be classified as a locator, a name, or both. A "Uniform Resource
    Locator" (URL) refers to the subset of URIs that, in addition to identifying
    a resource, provide a means of locating the resource by describing its
    primary access mechanism (e.g., its network "location").

    A common misunderstanding of URIs is that they are only used to refer to 
    accessible resources.  The URI itself only provides identification; access 
    to the resource is neither guaranteed nor implied by the presence of a URI.
    
    Namespaces in XML 1.0
    http://www.w3.org/TR/REC-xml-names/
    
*Constants*:
.. data:: constant_1
        
    constant_1 is used for ......
        
.. data:: constant_2
        
    constant_2 is used for ......
"""

# Imports
#
import logging
from urlparse import urlparse

# Constants
#

# Classes
#

class Uri(object):
    """This class simplifies the construction and manipulation of URI's. Each
    Uri has a base and a relative part. The base includes the namespace.

    *Usage examples*::
        uri = Uri('internal:')
        print(uri.type.root)
        if uri.type.root == 'http://node.internal/type/root':
            print('hello world!')

    *Common Arguments for Class methods*:
        <arguments>
    
    *Audience*:
        All developers.
    """
    
    # Class constants (will be updated as needed).
    namespaces = {
        'term':     'http://node.internal/data_dictionary/',
        'dc':       'http://purl.org/dc/elements/1.1/',
        'internal': 'http://node.internal/'
        }

    @classmethod
    def add_namespace(cls, ns, uri):
        cls.namespaces[ns] = uri
        
    def __init__(self, uri=None):
        """
        Called recursively until the desired URI is constructed.
        
        uri is the base of the uri that may be added to, e.g. "uri.a.b".
         
        Leading and trailing whitespace is removed from the string  
        representation of uri. This allows a Uri() to be passed to either 
        parameter and/or for its source to be an XML element with whitespace to 
        improve readability.
         
        If namespaces is specified, then URI will perform namespace expansion
        base on the dictionary passed to it.
        
        Parameters:
            uri :
            namespaces : 
        """
        
        #self.logger = logging.getLogger('yarra.datastore.uri.Uri')
        
        if uri is None:
            self._uri = None
        else:
            self._uri = str(uri).strip()
        
    def __getattr__(self, name):
        """This function allows the "." notation of Uri's. This is called when
        an attribute lookup has not found the attribute in the usual places
        (i.e. it is not an instance attribute nor is it found in the class tree  
        for self). Here we use it to add the attribute 'name' as the trailing  
        path element in the current instance of Uri.
        
        :param name: string - Uri path name
        :rtype: Uri
        """
        
        return self.add(name)

    def add(self, name):
        """This function adds the argument 'name' at the end of this URI.
        Note: whitespace is removed.
        :param name: string or any type that supports str() - Uri path name
        :rtype: Uri
        """
        
        uri = self._add(str(name).strip())
        self.__dict__[str(name).strip()] = uri
        return uri

    def _add(self, name):
        """This function returns a Uri with the argument 'name' at the end
        of the URI.
        
        :param name: string - Uri path name
        :rtype: Uri
        """
        
        if self._uri is None:
            uri = Uri(name)
        else:
            if str(self).endswith('/'):
                # a partial URI
                uri = Uri(self._uri + name)
            elif str(self).endswith(':'):
                # a namespace string
                uri = Uri(self._uri + name)
            else:
                uri = Uri(self._uri + '/' + name)
        return uri

    def uri_copy(self):
        """This function is a copy constructor. 
        
        :rtype: Uri - a copy of the current instance of Uri.
        """
        
        return Uri(uri=self._uri)

    def match(self, prefix_uri):
        """This function returns True if the start of the current instance of Uri
        matches the argument 'prefix_uri'.
        
        :param prefix_uri: Uri - the Uri prefix to match.
        :rtype: Boolean - True if the start of the current instance of Uri
        matches the argument 'prefix_uri'.
        """
        return str(self).startswith(str(prefix_uri))

    def collection(self, coll=True):
        """This function turns the current URI instance into a collection 
        ('coll' = True or None) or out of a collection ('coll' = False). 
        A collection is created by appending a slash (if there isn't already a 
        trailing slash.
        
        :param coll: Boolean - True (or None) if current Uri instance is to be 
        turned into a collection.
        :rtype: Uri - the current instance of Uri
        """
        
        # No change needed if you want to convert to/from collection when the uri
        # is already is/not collection
        if (coll and self.is_collection()) or \
            (not coll and not self.is_collection()):
            return self
        
        if (coll is True): # make a collection
            if (self._uri is None):
                self._uri = '/'
            else:
                self._uri += '/'
        else: # remove trailing slash
            if (self._uri is not None):
                if (self._uri.endswith(':') and \
                        (self._uri[:-1] in Uri.namespaces.keys())):
                    self._uri = self.full_uri()[:-1]
                else:
                    self._uri = self._uri[:-1]
        return self

    def is_collection(self):
        """
        This function returns True if the current instance of Uri is a 
        collection (i.e. it ends with a slash '/').
        
        :rtype: Boolean - True if the current instance of Uri is a collection.
        """
        
        return self.full_uri().endswith('/')    
      
    def split(self):
        """DEPRECIATED.
        This function splits the current instance of Uri into a List of 
        parts. The List is returned. For example:
        
            Uri('http://aaa.com:80/aa/bb/cc/dd/ee').split()
            
        returns a list:
        
            ['http://aaa.com:80', 'aa', 'bb', 'cc', 'dd', 'ee']
            
        "?" query and "#" fragment parts are ignored.
            
        Using '/'.join(list) will result in the original uri.
        
        A URI of None or '' will return None.
        
        :rtype: None or List - list of Uri parts.
        """
        
        if self._uri is None:
            return None
        else:
            uri = str(self.full_uri())
            bits = urlparse(uri)
            if uri is '':
                return None
            elif bits.scheme is '':
                return uri.split('/')
            elif (bits.netloc is '') and (bits.path is not ''):
                return uri.split('/') # [uri] 
            elif (bits.netloc is '') and (bits.path is ''):
                return uri.split('/')
            else:
                # if the path is just an empty string
                if bits.path is '':
                    return [bits.scheme + '://' + bits.netloc]
                elif bits.path == '/':
                    return [bits.scheme + '://' + bits.netloc, '']
                else:
                    return [bits.scheme + '://' + bits.netloc] + \
                        bits.path.split('/')[1:]

    def split2(self):
        """This function is replacement to split where 
        ''.join(list) will give the original uri.
        
        For the schemes http, https, ftp and file:
            - the first element is the scheme, domain and port, 
              e.g. 'http://aaa.com:80/'
            - the path is split on slash and each element is terminated by 
              that slash.
            - URL fragments are supported.
            
        For the scheme urn:
            - the URI is split on colon.
            
        For the scheme mailto:
            - the URI is split into mailto: and the rest of the string.
            
        If there is no scheme, the path is split on slash.
        If the scheme is unknown, it is terminated with a colon and split on a 
        slash.
        
        If the the joined output and the original string are not the same, the
        original string is returned. The exception has been removed.

        :rtype: None or List - list of Uri parts.
        """
        
        if self._uri is None:
            return None
        else:
            uri = str(self.full_uri())
            bits = urlparse(uri, '', True)
            #print('Processing URI: %s (s:%s n:%s p:%s f:%s)' % \
            #    (uri, bits.scheme, bits.netloc, bits.path, bits.fragment))
            # the simplest case
            if uri is '':
                return None
            # handle the base (scheme + domain + port) and set the split_char
            if bits.scheme in ['http', 'https', 'ftp', 'file']:
                split_char = '/'          
                base = bits.scheme + '://' + bits.netloc
                path = bits.path
                #print('Scheme base: %s' % base)
                new_path = [base]
            elif bits.scheme in ['urn']:
                split_char = ':'
                base = bits.scheme + split_char
                #print('Scheme (urn): %s' % base)
                path = bits.path
                new_path = [base]
            elif bits.scheme in ['mailto']:
                split_char = None
                #print('Scheme (mailto): %s' % bits.path)
                new_path = ['mailto:', bits.path]
            elif bits.scheme is '':
                split_char = '/'
                #print('No scheme.')
                new_path = []
                path = uri
            else:
                # just do the best we can
                split_char = '/'
                #print('Unsupported scheme: %s' % bits.scheme)
                new_path = [bits.scheme + ':']
                path = bits.path
            # handle the path with a split_char of slash or colon.
            if split_char is not None:
                # handle the case where split_char should be part of base,
                # in path_parts[0], not the first character in the path.
                if (len(new_path) > 0) and (not new_path[0].endswith(split_char)) and (path.startswith(split_char)):
                    new_path[0] += split_char
                    path = path[1:]
                #
                path_parts = path.split(split_char)
                #print('Path: %s' % repr(path_parts))
                for p in path_parts[:-1]:
                    #print('Path part: %s' % p + split_char)
                    new_path.append(p + split_char)
                if path_parts[-1] is not '':
                    #print('Path part: %s' % path_parts[-1])
                    new_path.append(path_parts[-1])
            # handle a fragment
            if bits.fragment is not '':
                new_path.append('#' + bits.fragment)
            # check that we can still join it up as the original URI
            if ''.join(new_path).lower() == uri.lower():
                return new_path
            else:
                #raise InvalidURLException(uri)
                # A change to the logic, just return the URI if it cannot be split.
                #print("Join failed %s %s" % (''.join(new_path).lower(), uri.lower()))
                return [uri]
        
    def fragmented(self):
        """This function returns this uri as a tuple consisting of the Uri 
        without the fragment, and the fragment as a string.
        
        :rtype: (Uri, fragment).
        """
        if self._uri is None:
            return (None, None)
        else:
            bits = self._uri.split('#')
            if len(bits) == 2:
                return (Uri(bits[0]), bits[1])
            else:
                return (Uri(bits[0]), None)
        
    def without_fragment(self):
        """This function returns this uri as a Uri without any fragment.
        
        :rtype: Uri
        """
        return self.fragmented()[0]
        
    def fragment(self):
        """This function returns the fragment portion of this Uri.
        
        :rtype: fragment (without the '#' character).
        """
        return self.fragmented()[1]
        
    def url(self):
        """This function parses the current instance of Uri as a URL and returns
        a ParseResult tuple representation.
        
        :rtype: ParseResult tuple.
        """
        
        return urlparse(self.full_uri())
  
    def full(self):
        """This function returns a new instance of Uri that is a full copy of 
        the current instance.
        
        :rtype: Uri
        """
        
        return Uri(self.full_uri())

    def full_uri(self):
        """This function returns a string representation of the current instance 
        of Uri. The parts of a URI can always just be joined. Trailing slashes 
        must be correctly includes in any namespace.
        
        :rtype: string
        """
        
        if self._uri is None:
            return ''
        else:
            parts = self._uri.split(':', 1)
            if parts[0] in Uri.namespaces.keys():
                parts[0] = str(Uri.namespaces[parts[0]])
                return ''.join(parts)
            else:
                return str(self._uri)
            
    def __str__(self):
        """This function returns the string representation of this uri with its 
        namespace intact.
        
        This function is called by Python's str().
        
        :rtype: string
        """

        if self._uri is None:
            return ''
        else:
            return self._uri

    def __len__(self):
        """This function returns the length of the full Uri string. 
        
        This function is called by Python's len().
        
        :rtype: int
        """
        
        return len(self.full_uri())

    def __cmp__(self, other):
        """This function performs a string comparison of the current instance's 
        full Uri with the full representation of Uri 'other'. It should return 
        a negative integer if self < other, zero if self == other, a positive 
        integer if self > other.
        
        This function is called when comparing the current Uri instance with
        'other'.
        
        :param other: Uri
        :rtype: int
        """
        
        s = self.full_uri()
        if type(other) == type(Uri()):
            o = other.full_uri()
        else:
            o = str(other)
        return cmp(s, o)
        
    def __add__(self, other):
        """
        This function appends the Uri instance 'other' to the current instance
        and returns the new Uri.
        
        This function is called by Python when the Uri instance 'other' is being 
        added to the current instance with the '+' operator.
        
        Note: The current instance is NOT effected by this function.
        Note: Whitespace is removed.
        
        :param other: Uri or any type that supports str()
        :rtype: Uri
        """
        
        return self._add(str(other).strip())

        
